import { isSameVnode } from "../utils";

export function patch(oldVnode,vnode) {
  if (!oldVnode){//组件的挂载流程 oldVnode为null 虚拟节点vnode是有的
    return createElm(vnode);//产生一个组件的真实节点
  }
  //unmount
  const isRealElement = oldVnode.nodeType;//元素节点类型1

  if (isRealElement){
    //删除老节点 根据vnode创建新节点，替换掉老节点
    const elm = createElm(vnode);
    const parentNode = oldVnode.parentNode;
    parentNode.insertBefore(elm, oldVnode.nextSibling);
    parentNode.removeChild(oldVnode);
    return elm;//返回最新的节点
  }else{//不管怎么diff 最终想更新渲染 =》 dom操作里去

    //- 只比较同级 如果不一样 儿子就不用对比了 根据当前节点 创建儿子 全部替换
    //diff算法如何实现
    console.log(oldVnode,vnode);
    if(!isSameVnode(oldVnode,vnode)){//如果新旧节点 不是同一个 删除老的换成新的
      return oldVnode.el.parentNode.replaceChild(createElm(vnode),oldVnode.el);
    }

    //- 文本直接更新即可，因为文本没有儿子
    let el = vnode.el = oldVnode.el;//复用节点
    if(!oldVnode.tag){//文本了 一个是文本 那么另一个一定也是文本
      if(oldVnode.text !== vnode.text){
        return el.textContent = vnode.text;
      }
    }

    //元素 新的虚拟节点
    updateProperties(vnode,oldVnode.data)
    //是相同节点了,复用节点，再更新不一样的地方（属性）

    //比较儿子节点
    let oldChildren = oldVnode.children||[];
    let newChildren = vnode.children||[];

    //情况1:老的有儿子 新的没有儿子
    if(oldChildren.length>0 && newChildren.length==0){
      el.innerHTML = "";
      //新的有儿子 老的没儿子 直接将新的值插入即可
    }else if(newChildren.length>0){
      newChildren.forEach(child => el.appendChild(createElm(child)))
    }else{
      //新老的都有儿子
      updateChildren(el,oldChildren,newChildren);
    }

    //返回新节点
    return el;
  }
}

//面试问：虚拟节点的实现 =》 如何将虚拟节点渲染成真是节点
export function createElm(vnode) {
  debugger
  let { tag, data, children, text, vm } = vnode;
  console.log("children",children);
  if (typeof tag === 'string') {
    if (createComponent(vnode)) { // 返回一个组件的真实节点
      return vnode.componentInstance.$el; // 返回组件的 对应的就是真实节点
    }
    vnode.el = document.createElement(tag); // 先创建id app
    updateProperties(vnode);
    children&&children.forEach(child => { // 在去查找id app的儿子，对儿子进行创建 {tag:'组件',componentOpions}
      vnode.el.appendChild(createElm(child)); // createElm(child) 有创建有创建组件和元素的功能
    })
  } else {//文本的tag = undefined
    vnode.el = document.createTextNode(text);
  }
  return vnode.el
}


function updateChildren(el,oldChildren,newChildren) {
  //vue2中如何做的diff算法
  //vue内部做了优化（能尽量提升性能 如果实在不行 再暴力比对）
  //1.在列表中新增和删除的情况
  let oldStartIndex=0;
  let oldStartVnode=oldChildren[0];
  let oldEndIndex = oldChildren.length -1;
  let oldENdVnode = oldChildren[oldEndIndex];

  let newStartIndex=0;
  let newStartVnode=newChildren[0];
  let newEndIndex = newChildren.length -1;
  let newEndVnode = newChildren[newEndIndex];

  function maKeyByIndex(children) {
    let map = {};
    children.forEach((item,index)=>{
      map[item.key] = index;
    })
    return map;
  }

  let mapping = makeKeyByIndex(oldChildren);


  //diff算法的复杂度 O(n) 比对的时候 指针交叉的时候 就是比对完成了【O(1)就是常量级，O(1)就是循环一次】
  while(oldStartIndex <= oldEndIndex && newStartIndex <=newEndIndex){
    if(!oldStartVnode){//在指针移动的时候 可能元素已经被移动走了 就跳过这一项
      oldStartVnode = oldChildren[++oldStartIndex]
    }else if(!oldEndVnode){
      oldEndVnode = oldChildren[--oldEndIndex]
    }
    //头头比
   if(isSameVnode(oldStartVnode,newStartVnode)){
     patch(oldStartVnode,newStartVnode);//会递归比较子节点 同时比较两个人的差异
     oldStartVnode = oldChildren[++oldStartIndex];
     newStartVnode = newChildren[++newStartIndex];

   } else if (isSameVnode(oldEndVnode, newEndVnode)) { // 尾尾比较
     patch(oldEndVnode, newEndVnode);
     oldEndVnode = oldChildren[--oldEndIndex];
     newEndVnode = newChildren[--newEndIndex];

   } else if (isSameVnode(oldStartVnode, newEndVnode)) { // 头尾
     patch(oldStartVnode, newEndVnode);
     el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling)
     oldStartVnode = oldChildren[++oldStartIndex];
     newEndVnode = newChildren[--newEndIndex];

   } else if (isSameVnode(oldEndVnode, newStartVnode)) { // 尾头
     patch(oldEndVnode, newStartVnode);
     el.insertBefore(oldEndVnode.el, oldStartVnode.el);// 将尾部的插入到头部去
     oldEndVnode = oldChildren[--oldEndIndex];
     newStartVnode = newChildren[++newStartIndex];
   } else {
     // 之前的逻辑都是考虑 用户一些特殊情况，但是有非特殊的，乱序排
     let moveIndex = mapping[newStartVnode.key];
     if (moveIndex == undefined) {
       // 没有直接将节点插入到开头的前面
       el.insertBefore(createElm(newStartVnode), oldStartVnode.el)
     } else {
       // 有的话需要复用
       let moveVnode = oldChildren[moveIndex]; // 找到复用的那个人，将他移动到前面去
       patch(moveVnode, newStartVnode);
       el.insertBefore(moveVnode.el, oldStartVnode.el);
       oldChildren[moveIndex] = undefined; // 将移动的节点标记为空
     }
     newStartVnode = newChildren[++newStartIndex]
   }
  }

  //新的多 那么就将多的插入进去即可
  if(newStartIndex <= newEndIndex){
    //如果下一个是Null 就是appendChild
    //不变
    let anchor = newChildren[newEndIndex + 1] == null ? null : newChildren[newEndIndex + 1].el;
    for(let i = newStartIndex;i<=newEndIndex;i++){
      //看一下 当前尾节点的下一个元素是否存在 如果是存在则插入到下一个元素的前边
      //如果下一个是null 就是appendChild
      //可能向前追加 可能向后追加
      el.insertBefore(createElm(newChildren[i]), anchor)
    }
    //老的多余的 需要清理掉 直接删除即可
    if(oldStartIndex <= oldEndIndex){
      for(let i = oldStartIndex;i<=oldEndIndex;i++){
        let child = oldChildren[i];//因为child可能是undefined 所以要跳过空节点
        child&&el.removeChild(child.el);
      }
    }
  }
}

function updateProperties(vnode,oldProps={}) {
  //这里的逻辑可能是初次渲染 初次渲染  直接oldProps 给vnode的el赋值即可
  //更新逻辑  拿到老的props和vnode里面的data进行对比
  let el = vnode.el;//dom真实的节点
  let newProps = vnode.data||{};
  
  //let newProps = vnode.data || {};
  let newStyle = newProps.newStyle ||{};
  let oldStyle = oldProps.style || {};

  //老的样式新的没有 直接干调
  for(let key in oldStyle){
    if(!newStyle[key]){
      el.style[key] = "";
    }
  }

  //新旧比对 两个对象如何比对差异？
  for(let key in newProps){
    if(key == 'style'){
      for(let key in newStyle){//{style:{color:red}}
        el.style[key] = newStyle[key]
      }
    }else{//直接用新的改掉老的就可以了
      //如果老的和新的一样 浏览器会去检测
      el.setAttribute(key,newProps[key])
    }
    //如果前后一样 浏览器会检测
    console.log(newProps);
  }

  //老的有新的没有
  for(let key in oldProps){
    if(!newProps[key]){
      el.removeAttribute(key)
    }
  }

}

function createComponent(vnode) { // 给组件预留了 一个初始化流程 init
  let i = vnode.data;//对象 有hook说明是组件 是init 进入组件的初始化了
  if ((i = i.hook) && (i = i.init)) {
    i(vnode)
  }
  if (vnode.componentInstance) { // 说明是组件
    return true;
  }
}